package service.validation;

import javax.annotation.Resource;

import entities.Sheet;
import entities.Status;

import org.springframework.stereotype.Service;
import org.springframework.validation.Errors;

import service.configuration.IBlockConfig;
import service.configuration.IConfiguration;
import service.configuration.IFieldConfig;
import service.configuration.IFieldDateConfig;
import service.configuration.IFieldEnumConfig;
import service.configuration.IFieldNumberConfig;
import service.configuration.IFieldStringConfig;
import service.configuration.IFieldTextConfig;
import service.configuration.ISheetConfig;
import service.configuration.xstream.FieldConfig;
import web.form.Block;
import web.form.Item;
import web.form.SheetView;

/// TODO : plein de redondance dans tous les sens à revoir l'architecture

@Service
public class SheetValidator implements ISheetValidator {
	

	private static final int stringMaxLenght = 255;
	private static final int textMaxLenght = 2147483647; // 4294967295;
	
	@Resource
	IConfiguration config;
	
	private final DateValidator dateValidator;
	private final EnumValidator enumValidator;
	private final NumberValidator numberValidator;
	private final StringValidator stringValidator;
	private final TextValidator textValidator;
	private final FieldValidator fieldValidator;
	
	public SheetValidator() {
		dateValidator = new DateValidator();
		enumValidator = new EnumValidator();
		numberValidator = new NumberValidator();
		stringValidator = new StringValidator();
		textValidator = new TextValidator();
		fieldValidator = new FieldValidator();
	}

	protected boolean isError(Status status) {
		return status != Status.Valid;
	}
	
	/**
	 * Comparaison de gravité d'erreur
	 * 
	 * @param s1 premier status à comparer.
	 * @param s2 second status à conparer.
	 * @return l'erreur la plus grave entre s1 et s2
	 */
	private Status biggestError(Status s1, Status s2) {
		if (s1 == Status.Unknown || s2 == Status.Unknown) {
			return Status.Unknown;
		}
		if (s1 == Status.HardError || s2 == Status.HardError) {
			return Status.HardError;
		}
		if (s1 == Status.SoftError || s2 == Status.SoftError) {
			return Status.SoftError;
		}
		return Status.Valid;
	}
	
	/**
	 * Teste la validité de la valeur du champs passé en paramètre, 
	 * par rapport au erreurs dure qui implique une impossibilité de persister
	 * la fiche.
	 * 
	 * @param value la valeur du champs à tester
	 * @param fieldConfig configuration du champs à tester
	 * @return le status HardError ou Valid
	 */
	private Status validateHardConstaintField(String value, IFieldConfig fieldConfig) {
		if (value == null) {
			return FieldValidator.statusValid;
		} else if (fieldConfig.getHibernateType().equals("string")) {
			if (value.length() > stringMaxLenght)
				return Status.HardError;
		} else if (fieldConfig.getHibernateType().equals("text")) {
			if (value.length() > textMaxLenght)
				return Status.HardError;
		}
		return Status.Valid;
	}
	
	/**
	 * Teste la validité d'un champs d'une SheetView.
	 * 
	 * @param name le nom du champs à tester
	 * @param value la valeur du champs à tester
	 * @param fieldConfig configuration du champs à tester
	 * @param errors mapping permettant de lier erreurs et messages
	 * @return le status SoftError ou Valid
	 */
	private Status validateSheetViewField(String name, String value, IFieldConfig fieldConfig, Errors errors) {
		if (value == null) {
			return FieldValidator.statusValid;
		}
		
		Status status = validateHardConstaintField(value, fieldConfig);
		if (isError(status))
			return status;
		
		status = fieldValidator.validate(name, value, fieldConfig, errors);
		if (isError(status))
			return status;
		
		if (fieldConfig.isOptional() && fieldValidator.isEmpty(value, fieldConfig))
			return status;
		
		switch (fieldConfig.getType()) {
		case date:          return dateValidator.validate(name, value, (IFieldDateConfig)fieldConfig.getTypeConfig(), errors);
		case number:        return numberValidator.validate(name, value, (IFieldNumberConfig)fieldConfig.getTypeConfig(), errors);
		case string:        return stringValidator.validate(name, value, (IFieldStringConfig)fieldConfig.getTypeConfig(), errors);
		case text:          return textValidator.validate(name, value, (IFieldTextConfig)fieldConfig.getTypeConfig(), errors);
		case enumaration:   return enumValidator.validate(name, value, (IFieldEnumConfig)fieldConfig.getTypeConfig(), errors);
		default:            return FieldValidator.statusValid;
		}
	}
	
	/**
	 * Teste la validité d'une SheetView.
	 * Le status de la SheetView est affecté à la nouvelle valeur.
	 * 
	 * @param view la SheetView à tester
	 * @param errors mapping permettant de lier erreurs et messages
	 * @return le status HardError, SoftError ou Valid
	 */
	public Status validate(SheetView view, Errors errors) {
		view.setStatus(Status.Valid);
		
		int blocksCount = 0;
		for (Block block : view.getBlocks()) {
			
			int itemsCount = 0;
			for (Item item : block.getItems()) {
				
				IFieldConfig fieldConfig = config.getFieldConfig(view.getType(), item.getKey());
				if (fieldConfig != null && ! fieldConfig.isReadOnly()) {
					item.setStatus(
							validateSheetViewField(
									"blocks[" + blocksCount + "].items[" + itemsCount + "].value",
									item.getValue(), 
									config.getFieldConfig(view.getType(), item.getKey()),
									errors
									)
							);
					
					view.setStatus(biggestError(view.getStatus(), item.getStatus()));
				}
				itemsCount ++;
			}
			blocksCount ++;
		}
		
		switch (view.getStatus()) {
			case Valid : break;
			case Unknown :
			case SoftError : errors.reject("error.badSheet"); break;
			case HardError : errors.reject("error.veryBadSheet"); break;
		}
		
		return view.getStatus();
	}
	
	/**
	 * Teste la validité d'un champs d'une Sheet.
	 * 
	 * @param value la valeur du champs à tester
	 * @param fieldConfig configuration du champs à tester
	 * @return le status SoftError ou Valid
	 */
	public Status validateSheetField(String value, IFieldConfig fieldConfig) {
		
		Status status = validateHardConstaintField(value, fieldConfig);
		if (isError(status))
			return status;
		
		status = fieldValidator.validate(value, fieldConfig);
		if (isError(status))
			return status;
		
		if (fieldConfig.isOptional() && fieldValidator.isEmpty(value, fieldConfig))
			return status;
		
		switch (fieldConfig.getType()) {
		case date:          return dateValidator.validate(value, (IFieldDateConfig)fieldConfig.getTypeConfig());
		case number:        return numberValidator.validate(value, (IFieldNumberConfig)fieldConfig.getTypeConfig());
		case string:        return stringValidator.validate(value, (IFieldStringConfig)fieldConfig.getTypeConfig());
		case text:          return textValidator.validate(value, (IFieldTextConfig)fieldConfig.getTypeConfig());
		case enumaration:   return enumValidator.validate(value, (IFieldEnumConfig)fieldConfig.getTypeConfig());
		default:            return FieldValidator.statusValid;
		}
	}
	
	/**
	 * Teste la validité d'une Sheet.
	 * Le status de la Sheet est affecté à la nouvelle valeur.
	 * 
	 * @param sheet la Sheet à tester
	 * @return le status HardError, SoftError ou Valid
	 */
	public Status validate(Sheet sheet) {
		Status itemStatus;
		sheet.setStatus(Status.Valid);
		
		ISheetConfig sheetConfig = config.getSheetConfig(sheet.getType());
		for (IBlockConfig blockConfig : sheetConfig.getBlockConfigs()) {
			for (IFieldConfig field : blockConfig.getFieldConfigs()) {
				IFieldConfig fieldConfig = config.getFieldConfig(sheet.getType(), field.getName());
				if (fieldConfig != null && ! fieldConfig.isReadOnly()) {
					itemStatus = validateSheetField(
							(String) sheet.getProperty(field.getName()),
							fieldConfig);
					sheet.setStatus(biggestError(sheet.getStatus(), itemStatus));
				}
			}
		}
		return sheet.getStatus();
	}
	
	
}
